package gates;

import register.Register;
import gates.FunctionGate;
import mathtools.Complex;
/**
 * Functional representation of a hadamard gate.
 * This implements Gate interface, so consult this for public methods.
 * The constructor for this gate is protected, so generating such an object is left
 * for the factory class <code>Factory</code>
 * @author Jan Zaucha, Alex Bush
 *
 */
public class HadamardFunction extends FunctionGate {

	/** Store the target qubit the gate acts on*/	
	private int target;


	/**
	 * Protected constructor for a functional representation of a hadamard gate
	 * Requires a target bit for the gate to operate on, however if the target bit is
	 * -1 the gate will hadamard transform all qubits
	 * @param target the target bit to hadamard transform (counting qubits from 0)
	 */
	protected HadamardFunction(int target){
		this.target = target+1; // since first qubit is given by 0, add one so first qubit is 1, 2nd is 2 etc...
		                        //we then hadamard transform all if target is 0.. 
	}

	/**
	 * Returns the name of the gate regardless of representation.
	 */
	public String getName() {
		return "Hadamard";
	}

	/**
	 * Applies this gate to the given register. See interface <code>Gate</code> for usage.
	 * @param r the register 
	 */
	public void apply(Register r) {
		if (target > r.getNumQbits() || target < 0){
			throw new IllegalArgumentException("Target qubit not in register. Register size (qubits): " + r.getNumQbits() + " Target qubit: " + target);
		}

		int numStates = r.getNumStates();    // number of states in register
		// create array to store updated register
		Complex [] newRegister = new Complex [numStates]; 

		// if target is flagged "-1" we hadamard transform all qubits
		if(target==0){
			// if register is in bold state |0> or |1> we know the answer straight away
			if(r.getElement(0, 0).getMagnitudeSquared() ==  1.0 || r.getElement(0, numStates -1).getMagnitudeSquared() ==  1.0 ){
				for(int i=0; i<numStates; i++){
					newRegister [i]= new Complex(1.0f,0.0f);	
				}
				r.update(newRegister);
				r.normalise();  // normalize register
			}
			// else we have to hadamard taransform all the qubits seperately 
			else{

				for(int j=1; j< r.getNumQbits()+1; j++){
					applyToSingleQubit(j, r);
				}
				r.normalise();
			}
		}
		// else apply to a given single qubit
		else if(target != 0){
			applyToSingleQubit(target, r);
			r.normalise();
		}
	}

	/**
	 * Private method that applies hadamard gate to the given register given the target qubit. 
	 * @param targetVar target qubit for the hadamard transform !(count qubits from 1 to N)!
	 * @param r the register 
	 */
	private void applyToSingleQubit(int targetVar, Register r){
		int numStates = r.getNumStates();    // number of states in register
		// create array to store updated register
		Complex [] newRegister = new Complex [numStates]; 
		// interval size- # of amplitudes in one interval of "function"
		int intervalSize = (int)(numStates/Math.pow(2,targetVar));
		// first j loop for number of intervals of "same sign"
		for(int j=0; j< Math.pow(2, targetVar -1); j++){
			// nested loop: first add amplitudes, then subtract 
			for(int i=1; i <intervalSize +1; i++){
				newRegister[2*j*intervalSize +i-1] = Complex.addComplex(r.getElement(0, 2*j*intervalSize +i-1), r.getElement(0, intervalSize+2*j*intervalSize +i-1));
				newRegister[intervalSize +2*j*intervalSize +i-1] = Complex.addComplex(r.getElement(0, 2*j*intervalSize +i-1), Complex.multiplyComplex(-1, r.getElement(0, intervalSize+2*j*intervalSize +i-1)));
			}

		}
		// Now update our real qubits 
		r.update(newRegister);
		
	}

}
